#include <vector>
#include <numeric>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::ReturnPointee;
using ::testing::SetArgPointee;
using ::testing::AnyNumber;
using ::testing::NewPermanentCallback;
using ::testing::Invoke;
using ::testing::Gt;
using ::testing::_;
using ::testing::Lt;
using ::testing::DoAll;

class Interface
{
public:
  virtual int getLevel(int x,int y)=0;
};

class TestMock :public Interface
{
public:
  MOCK_METHOD(int , getLevel,(int,int));
};

TEST(GMockTest,TestDefault)
{
  TestMock mockObject;
  ON_CALL(mockObject,getLevel(_,_))
    .With(Lt())
    .WillByDefault(Return(100));

  EXPECT_CALL(mockObject,getLevel(0,0))
    .WillOnce(Return(1))
    .WillRepeatedly(Return(-1));
  EXPECT_EQ(mockObject.getLevel(0,0),1);
  EXPECT_EQ(mockObject.getLevel(0,0),-1);

  EXPECT_EQ(mockObject.getLevel(0,0),-1);
  //EXPECT_EQ(mockObject.getLevel(10,10),100);

}


class Bar{
public:
};

class Foo {
 public:
  virtual ~Foo(){};

  virtual void Pure(int n) = 0;
  virtual int Concrete(const char* str)
  {
    return 0;
  }
  virtual int Sign(int n) = 0;
  virtual Bar& GetBar() = 0;
  virtual int GetValue() = 0;
  virtual int Sum(int x ,int y) = 0;
  virtual bool ComplexJob(int x) = 0;
};


class MockFoo : public Foo {
 public:
  // Mocking a pure method.
  MOCK_METHOD(void, Pure, (int n), (override));
  // Mocking a concrete method.  Foo::Concrete() is shadowed.
  MOCK_METHOD(int, Concrete, (const char* str), (override));

  MOCK_METHOD(int , Sign,(int n),(override));
  MOCK_METHOD(Bar& ,GetBar,(),(override));
  MOCK_METHOD(int,GetValue,(),(override));
  MOCK_METHOD(int, Sum, (int x, int y), (override));
  MOCK_METHOD(bool, ComplexJob, (int x), (override));
};

TEST(TestGMock , OnCallTest)
{
  MockFoo foo;
  ON_CALL(foo, Sign(_))
      .WillByDefault(Return(-1));
  ON_CALL(foo, Sign(0))
      .WillByDefault(Return(0));
  ON_CALL(foo, Sign(Gt(0)))
      .WillByDefault(Return(1));

  EXPECT_CALL(foo, Sign(_))
      .Times(AnyNumber());

  EXPECT_EQ( 1,foo.Sign(5));   // This should return 1.
  EXPECT_EQ( -1,foo.Sign(-9));  // This should return -1.
  EXPECT_EQ( 0,foo.Sign(0));   // This should return 0.
}

/*
Test(TestMatcher, ReturnTest)
{
  MockFoo foo;
  EXPECT_CALL(foo,Pure(Ge(5)))
    .WillOnce(Return('a'));
  EXPECT_CALL(foo,Concrete("Hello",NotNull));


  EXPECT_EQ(foo.DoThis(5),);
  EXPECT_EQ(foo.DoThat(""),);
}
*/
TEST(TestAction , ReturnRefTest)
{
  MockFoo foo;
  Bar bar;

  EXPECT_CALL(foo,GetBar())
    .WillOnce(ReturnRef(bar));
  
  Bar& barRef = foo.GetBar();
}


TEST(TestAction , IntRefTest)
{
  int x = 0;
  MockFoo foo;

  EXPECT_CALL(foo,GetValue())
    .WillRepeatedly(ReturnPointee(&x));
  
  x = 42;
  EXPECT_EQ(42,foo.GetValue());
}

class Mutator{
public:
  virtual bool MutateInt(int * value) = 0;
};

class MockMutator : public Mutator {
 public:
  MOCK_METHOD(bool, MutateInt, (int* value), (override));
};

TEST(TestAction,ArgPointeeTest)
{
  MockMutator mutator;
  EXPECT_CALL(mutator, MutateInt(_))
      .WillOnce(DoAll(SetArgPointee<0>(5),
                      Return(true)));  
}

int CalculateSum(int x, int y) { return x + y; }
int Sum3(int x, int y, int z) { return x + y + z; }

class Helper {
 public:
  bool ComplexJob(int x);
};

TEST(TestAction,FuncActionTest)
{
  MockFoo foo;
  Helper helper;
  EXPECT_CALL(foo, Sum(_, _))
      .WillOnce(&CalculateSum)
      .WillRepeatedly(Invoke(NewPermanentCallback(Sum3, 1)));
  EXPECT_CALL(foo, ComplexJob(_))
      .WillOnce(Invoke(&helper, &Helper::ComplexJob))
      .WillOnce([] { return true; })
      .WillRepeatedly([](int x) { return x > 0; });

  foo.Sum(5, 6);         // Invokes CalculateSum(5, 6).
  foo.Sum(2, 3);         // Invokes Sum3(1, 2, 3).
  foo.ComplexJob(10);    // Invokes helper.ComplexJob(10).
  foo.ComplexJob(-1);    // Invokes the inline lambda.
}




int main(int argc,char* argv[])
{
    ::testing::InitGoogleMock(&argc,argv);
    return RUN_ALL_TESTS();
}